package com.sh.caryr.erp.controller.trade;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.sh.caryr.erp.entity.PageResultMessage;
import com.sh.caryr.erp.entity.ResultMessage;
import com.sh.caryr.erp.entity.basis.CompCfg;
import com.sh.caryr.erp.entity.definition.CommonFieldDef;
import com.sh.caryr.erp.entity.enums.OrderStatus;
import com.sh.caryr.erp.entity.product.ProductMapping;
import com.sh.caryr.erp.entity.product.ProductSpec;
import com.sh.caryr.erp.entity.trade.Order;
import com.sh.caryr.erp.entity.trade.OrderStatistics;
import com.sh.caryr.erp.entity.trade.TradeOrder;
import com.sh.caryr.erp.entity.user.User;
import com.sh.caryr.erp.excel.ImportFromExcel;
import com.sh.caryr.erp.exception.RestServiceException;
import com.sh.caryr.erp.exception.ResultCode;
import com.sh.caryr.erp.service.ICompCfgService;
import com.sh.caryr.erp.service.IProductMappingService;
import com.sh.caryr.erp.service.IProductSpecService;
import com.sh.caryr.erp.service.IOrderService;
import com.sh.caryr.erp.service.external.ExtService;
import com.sh.caryr.erp.service.external.ExtServiceFactory;
import com.sh.caryr.erp.service.order.IOrderStatisticsService;
import com.sh.caryr.erp.util.DateUtils;
import com.sh.caryr.erp.util.ERPConstants;
import com.sh.caryr.erp.util.MyBatisUtil;
import com.sh.caryr.erp.util.UtilityTool;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import net.sf.json.JSONObject;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.*;

import static com.sh.caryr.erp.util.ERPConstants.NEW_OBJECT_ID;

@RestController
@RequestMapping("/api/order")
@SessionAttributes("user")
public class OrderController {

    private final IOrderService orderService;

    private final IProductSpecService productSpecService;

    private final IProductMappingService productMappingService;

    private final ExtServiceFactory extServiceFactory;

    private final IOrderStatisticsService orderStatisticsService;

    private final ICompCfgService compCfgService;

    @Autowired
    public OrderController(IOrderService orderService, IProductSpecService productSpecService,
                           IProductMappingService productMappingService, ExtServiceFactory extServiceFactory,
                           IOrderStatisticsService orderStatisticsService, ICompCfgService compCfgService) {
        this.orderService = orderService;
        this.productSpecService = productSpecService;
        this.productMappingService = productMappingService;
        this.extServiceFactory = extServiceFactory;
        this.orderStatisticsService = orderStatisticsService;
        this.compCfgService = compCfgService;
    }

    @ApiOperation(value = "Get Count", notes = "Get Count")
    @RequestMapping(value = "/count", method = RequestMethod.GET)
    @ResponseStatus(HttpStatus.OK)
    @SuppressWarnings("unchecked")
    public ResultMessage<JSONObject> countProducts(@ModelAttribute("user") User operator) {
        Wrapper wrapper = MyBatisUtil.addAuthFilter(MyBatisUtil.getIsActiveWrapper(), operator, ERPConstants.AUTH_TARGET_TRADE, false, true);
        JSONObject counts = new JSONObject();
        counts.put(CommonFieldDef.FRONT_COUNT_TOTAL, orderService.selectCount(wrapper));
        counts.put(CommonFieldDef.FRONT_COUNT_FINISH, orderService.selectCount(wrapper.ge(CommonFieldDef.ORDER_STATUS, OrderStatus.SUCCESS.getValue())));
        return new ResultMessage<>(counts);
    }

    @ApiOperation(value = "Refresh Statistics", notes = "Refresh Statistics")
    @RequestMapping(value = "/refreshStatistics", method = RequestMethod.GET)
    @ResponseStatus(HttpStatus.OK)
    public ResultMessage<List<OrderStatistics>> refreshStatistics(@ModelAttribute("user") User operator) {
        Wrapper<Order> wrapper = new EntityWrapper<>();
        MyBatisUtil.addAuthFilter(MyBatisUtil.getIsActiveWrapper(), operator, ERPConstants.AUTH_TARGET_TRADE, false, true);
        //Get the orders start from 12 month ago.
        wrapper.ge(CommonFieldDef.CREATE_AT, DateUtils.getStartOfMonthBy(-12)).ge(CommonFieldDef.ORDER_STATUS, OrderStatus.WAIT_RECEIVE.getValue());
        List<Order> records = orderService.selectList(wrapper);

        //TODO: Replace with batch query.
        for (Order record : records) {
            orderService.getOrderDetail(record);
        }
        //** get product specification list
        List<Long> proSpecIds = new ArrayList<>();
        for (Order record : records) {
            if (CollectionUtils.isNotEmpty(record.getTradeOrderList())) {
                orderService.getOrderDetail(record);
                for (TradeOrder order : record.getTradeOrderList()) {
                    proSpecIds.add(order.getProductSpecId());
                }
            }
        }
        List<ProductSpec> specs = CollectionUtils.isNotEmpty(proSpecIds) ? productSpecService.selectBatchIds(proSpecIds) : new ArrayList<ProductSpec>();
        List<OrderStatistics> list = getStatistics(records, specs, operator.getCoco());
        Wrapper<OrderStatistics> statWrapper = new EntityWrapper<>();
        statWrapper.eq(CommonFieldDef.COCO, operator.getCoco());
        // Delete old data
        orderStatisticsService.delete(statWrapper);
        // Insert new data
        orderStatisticsService.insertBatch(list);
        return new ResultMessage<>(list);
    }

    @ApiOperation(value = "Statistics", notes = "Get statistics for Dashboard")
    @RequestMapping(value = "/statistics", method = RequestMethod.GET)
    @ResponseStatus(HttpStatus.OK)
    public ResultMessage<List<OrderStatistics>> statistics(@ModelAttribute("user") User operator) {
        Wrapper<OrderStatistics> wrapper = new EntityWrapper<>();
        MyBatisUtil.addAuthFilter(MyBatisUtil.getIsActiveWrapper(), operator, ERPConstants.AUTH_TARGET_TRADE, false, true);
        wrapper.eq(CommonFieldDef.COCO, operator.getCoco());
        wrapper.orderBy("date", true);
        List<OrderStatistics> orderStatisticsList = orderStatisticsService.selectList(wrapper);
        if (CollectionUtils.isEmpty(orderStatisticsList)) {
            orderStatisticsList = new ArrayList<>(initOrderStatsMap(operator.getCoco()).values());
        }
        orderStatisticsList.get(orderStatisticsList.size() - 1).setBulletClass("lastBullet");
        return new ResultMessage<>(orderStatisticsList);
    }

    @ApiOperation(value = "获取订单列表信息", notes = "获取订单列表信息")
    @RequestMapping(method = RequestMethod.GET)
    @ResponseStatus(HttpStatus.OK)
    @SuppressWarnings("unchecked")
    public ResultMessage<List<Order>> findTrades(
            @RequestParam(defaultValue = "1") Integer current,
            @RequestParam(defaultValue = "20") Integer size,
            @RequestParam(name = "search[value]", required = false) String filters,
            @RequestParam(defaultValue = "id") String orderBy,
            @RequestParam(defaultValue = "false") Boolean isAsc,
            @RequestParam(defaultValue = "false") Boolean loadDetail,
            @ModelAttribute("user") User operator) {
        ResultMessage<List<Order>> result = MyBatisUtil.searchEntities(current, size, filters, orderBy, isAsc, operator, orderService);
        if (loadDetail) {
            List<Order> orders = result.getData();
            for (Order order : orders) {
                orderService.getOrderDetail(order);
            }
        }
        return result;
    }

    @ApiOperation(value = "获取单个订单信息", notes = "获取单个订单信息")
    @ApiImplicitParam(name = "id", value = "订单id", required = true, dataType = "long")
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    @ResponseStatus(HttpStatus.OK)
    public ResultMessage<Order> findTradeById(@PathVariable Long id) {
        Order order = orderService.selectById(id);
        if (id == NEW_OBJECT_ID) {
            order = new Order();
        } else if (order == null) {
            throw new RestServiceException(HttpStatus.NOT_FOUND, ResultCode.ENTITY_NOT_FOUND);
        } else {
            orderService.getOrderDetail(order);
        }
        return new ResultMessage<>(order);
    }

    @Transactional
    @ApiOperation(value = "创建订单", notes = "根据trade对象创建订单")
    @ApiImplicitParam(name = "trade", value = "订单详细实体trade", required = true, dataType = "Order")
    @RequestMapping(method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.OK)
    public ResultMessage<String> insertTrade(@RequestBody Order order, @ModelAttribute("user") User operator) {
        order.setCreateBy(operator.getId());
        order.setStatus(OrderStatus.DRAFT.getValue());//default status is DRAFT
        order.setCoco(operator.getCoco());
        orderService.insertOrderSkuAmount(order);
        return new ResultMessage<>(order.getId().toString());
    }

    /**
     * Ext(External Orders); Int(Internal Orders)
     * Mapping status
     * 1. Ext->Int(Not exists): import Ext orders
     * 2. Ext(Not exists)->Int: ignore
     * 3. Ext->Int Inconsistent: update order and stock
     * 4. Ext->Int Consistent: ignore
     */
    @Transactional
    @ApiOperation(value = "Synchronize 3rd party orders")
    @RequestMapping(value = "/syncOrder/", method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.OK)
    @SuppressWarnings("unchecked")
    public PageResultMessage<List<Order>> syncOrder(@RequestParam(name = "startDate", required = false) String startDate,
                                                             @RequestParam(name = "endDate", required = false) String endDate,
                                                             @RequestParam(name = "days", required = false) String daysStr,
                                                             @ModelAttribute("user") User operator) {
        List<CompCfg> compConfigs = compCfgService.loadCompCfgByCoco(operator.getCoco());
        List<Order> allUpdateList = new ArrayList<>();
        for (CompCfg compCfg : compConfigs) {
            ExtService extSrv = extServiceFactory.getExtService(compCfg);
            List<Order> orderList;
            if (StringUtils.isNotEmpty(startDate) && StringUtils.isNotEmpty(endDate)) {
                Date start = DateUtils.parseDate(startDate, DateUtils.DATE_FORMAT_FULL);
                Date end = DateUtils.parseDate(endDate, DateUtils.DATE_FORMAT_FULL);
                orderList = extSrv.searchOrder(start, end);
            } else {
                int days = extSrv.getCompCfg().getSyncOrderDays();
                if (StringUtils.isNotEmpty(daysStr)) {
                    try {
                        days = Integer.parseInt(daysStr);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                orderList = extSrv.searchOrderByDays(days);
            }

            //--Read ProductMappings
            Wrapper wrapper = MyBatisUtil.getIsActiveWrapper();
            wrapper.eq(CommonFieldDef.EXT_PLATFORM, compCfg.getExtPlatform());
            List<ProductMapping> proMapping = productMappingService.selectList(wrapper);
            Map<String, Long> proMappingM = new HashMap<>();
            if (proMapping != null) {
                for (ProductMapping proMap : proMapping) {
                    proMappingM.put(proMap.getExtProId(), proMap.getProductSpecId());
                }
            }
            for (Order trade : orderList) {
                MyBatisUtil.updateCommonFields(trade, operator);
                for (TradeOrder order : trade.getTradeOrderList()) {
                    Long productSpecId = proMappingM.get(order.getExtProductSpecId());
                    if (productSpecId != null) {
                        order.setProductSpecId(productSpecId);
                    }
                }
            }

            //--Read Old traders
            Wrapper tradeWrapper = MyBatisUtil.getIsActiveWrapper();
            List<String> extTxnIdList = new ArrayList<>();
            for (Order order : orderList) {
                extTxnIdList.add(order.getExtTxnId());
            }
            tradeWrapper.eq(CommonFieldDef.EXT_PLATFORM, compCfg.getExtPlatform());
            tradeWrapper.in(CommonFieldDef.ORDER_EXT_TXN_ID, extTxnIdList);
            List<Order> oldOrders = orderService.selectList(tradeWrapper);
            //--Get update list base on new & old trade lists.
            List<Order> updateList = getUpdateTradeList(orderList, oldOrders);
            //--Update trades
            try {
                orderService.updateOrderBatch(updateList);
            } catch (Exception e) {
                e.printStackTrace();
                throw new RestServiceException(ResultCode.UNKNOWN);
            }
            allUpdateList.addAll(updateList);
        }
        return new PageResultMessage<>(allUpdateList, allUpdateList.size());
    }

    @ApiOperation(value = "更新订单", notes = "根据Trade对象更新订单")
    @ApiImplicitParam(name = "trade", value = "订单详细实体trade", required = true, dataType = "Order")
    @Transactional
    @RequestMapping(method = RequestMethod.PUT)
    @ResponseStatus(HttpStatus.OK)
    public Object updateTrade(@RequestBody Order order, @ModelAttribute("user") User operator) {
        MyBatisUtil.updateCommonFields(order,operator);
        try {
            orderService.insertOrUpdateOrder(order);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RestServiceException(ResultCode.UNKNOWN);
        }
        return new ResultMessage<>(order);
    }

    @ApiOperation(value = "批量更新", notes = "批量更新")
    @RequestMapping(value = "/batch", method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.OK)
    public ResultMessage<List<Order>> batch(@RequestParam("ids") String[] ids, @RequestParam("updateStatus") String updateStatus) {
        List<Order> list = new ArrayList<>();
        Integer newStatus = Integer.parseInt(updateStatus);
        List<Long> idList = new ArrayList<>();
        if (ids == null || ids.length == 0) {
            throw new RestServiceException(HttpStatus.NOT_FOUND, ResultCode.ENTITY_NOT_FOUND);
        }
        for (String idStr : ids) {
            idList.add(Long.parseLong(idStr));
        }
        List<Order> orders = orderService.getOrderDetailByIds(idList);
        if (orders.isEmpty()) {
            throw new RestServiceException(HttpStatus.NOT_FOUND, ResultCode.ENTITY_NOT_FOUND);
        }
        for (Order order : orders) {
            order.setStatus(newStatus);
        }
        try {
            orderService.updateOrderBatch(orders);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RestServiceException(ResultCode.UNKNOWN);
        }
        return new ResultMessage<>(list);
    }

    @ApiOperation(value = "批量删除订单", notes = "根据url的ids来批量删除订单")
    @ApiImplicitParam(name = "ids", value = "订单ids", required = true, dataType = "String")
    @Transactional
    @RequestMapping(value = "/{ids}", method = RequestMethod.DELETE)
    @ResponseStatus(HttpStatus.OK)
    public ResultMessage deleteProductById(@PathVariable String ids) {
        List<Long> list = UtilityTool.convertStringArray2LongList(ids.split(","));
        try {
            orderService.deleteOrderById(list);
            return new ResultMessage();
        } catch (RestServiceException e) {
            throw e;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RestServiceException(ResultCode.DELETE_NOT_FOUND);
        }
    }

    @ApiOperation(value = "批量导入订单", notes = "excel批量导入订单")
    @RequestMapping(value = "/importTrades", method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.OK)
    public ResultMessage<List<Order>> importTrades(@RequestParam("file") MultipartFile file) {
        List<Order> list = new ArrayList<>();
        if (!file.isEmpty()) {

            try {
                list = ImportFromExcel.importTrade(file.getInputStream());
            } catch (Exception e) {
                throw new RestServiceException(ResultCode.IMPORT_EXCEL_FAILED);
            }
        }

        return new ResultMessage<>(list);
    }

    private List<Order> getUpdateTradeList(List<Order> newOrders, List<Order> oldOrders) {
        List<Order> updateList = new ArrayList<>();
        if (CollectionUtils.isEmpty(newOrders)) {
            //do nothing
            return updateList;
        } else if (CollectionUtils.isEmpty(oldOrders)) {
            updateList.addAll(newOrders);
        } else {
            for (Order newOrder : newOrders) {
                Order find = null;
                for (Order oldOrder : oldOrders) {
                    if (StringUtils.equals(newOrder.getExtTxnId(), oldOrder.getExtTxnId())) {
                        find = oldOrder;
                        break;
                    }
                }
                if (null == find) {
                    updateList.add(newOrder);
                } else if (!org.apache.commons.lang3.time.DateUtils.truncatedEquals(find.getUpdateAt(), newOrder.getUpdateAt(), Calendar.SECOND) ||
                        !newOrder.getStatus().equals(find.getStatus())) {
                    newOrder.setId(find.getId());
                    updateList.add(newOrder);
                }
            }
        }
        return updateList;
    }

    private List<OrderStatistics> getStatistics(List<Order> orders, List<ProductSpec> specList, String coco) {
        if (CollectionUtils.isNotEmpty(orders)) {
            Map<String, OrderStatistics> map = initOrderStatsMap(coco);
            Map<Long, ProductSpec> specMap = convertSpecList2Map(specList);
            for (Order trade : orders) {
                String dateStr = DateUtils.formatDateTime(trade.getCreateAt(), DateUtils.DATE_FORMAT_MON); //"date": "2018-05",
                OrderStatistics rest = map.get(dateStr);
                if (rest == null) {
                    continue;
                }

                float amount = 0;
                float skuCount = 0;
                for (TradeOrder order : trade.getTradeOrderList()) {
                    float proCount = order.getProductSpecAmount() != null ? order.getProductSpecAmount() : 0;//"sku_count": 443
                    float proPrice;
                    if (specMap.get(order.getProductSpecId()) != null && specMap.get(order.getProductSpecId()).getSalePrice() != null) {
                        proPrice = specMap.get(order.getProductSpecId()).getSalePrice();
                    } else {
                        proPrice = 0;
                    }
                    amount += proCount * proPrice;
                    skuCount += proCount;
                }
                rest.setAmount(rest.getAmount() + amount);
                rest.setSkuCount(rest.getSkuCount() + skuCount);
                rest.setOrderCount(rest.getOrderCount() + 1);
            }
            return new ArrayList<>(map.values());
        }
        return new ArrayList<>();
    }

    private Map<String, OrderStatistics> initOrderStatsMap(String coco) {
        Map<String, OrderStatistics> map = new TreeMap<>();
        int maxRange = 12;
        Calendar c = Calendar.getInstance();
        c.add(Calendar.MONTH, 1); //Start from next month
        for (int i = 0; i < maxRange; i++) {
            c.add(Calendar.MONTH, -1);
            String date = DateUtils.formatDateTime(c.getTime(), DateUtils.DATE_FORMAT_MON);
            OrderStatistics rest = OrderStatistics.builder().coco(coco).date(date).build();
            if (i == 0) { //current month;
                rest.setBulletClass("lastBullet");
            }
            map.put(date, rest);
        }
        return map;
    }

    private Map<Long, ProductSpec> convertSpecList2Map(List<ProductSpec> specs) {
        Map<Long, ProductSpec> map = new HashMap<>();
        if (CollectionUtils.isNotEmpty(specs)) {
            for (ProductSpec spec : specs) {
                map.put(spec.getId(), spec);
            }
        }
        return map;
    }
}
